diff --git a/requirements-base.txt b/requirements-base.txt
index f0d0389d530e..97db3619e514 100644
--- a/requirements-base.txt
+++ b/requirements-base.txt
@@ -114,7 +114,7 @@ deprecation==2.1.0
     # via pdpyras
 dnspython==2.4.2
     # via email-validator
-duo-client==5.1.0
+duo-client==5.2.0
     # via -r requirements-base.in
 ecdsa==0.18.0
     # via python-jose
@@ -212,7 +212,7 @@ lxml==4.9.3
     #   premailer
 mako==1.2.4
     # via alembic
-markdown==3.5
+markdown==3.5.1
     # via -r requirements-base.in
 markupsafe==2.1.3
     # via
@@ -278,7 +278,7 @@ preshed==3.0.8
     # via
     #   spacy
     #   thinc
-protobuf==4.24.4
+protobuf==4.25.0
     # via
     #   -r requirements-base.in
     #   google-api-core
@@ -382,7 +382,7 @@ scipy==1.11.2
     # via statsmodels
 sentry-asgi==0.2.0
     # via -r requirements-base.in
-sentry-sdk==1.32.0
+sentry-sdk==1.34.0
     # via
     #   -r requirements-base.in
     #   sentry-asgi
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 3e20adc3d858..c5c9820e99c2 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -10,8 +10,6 @@ asttokens==2.2.1
     #   stack-data
 attrs==22.1.0
     # via -r requirements-dev.in
-backcall==0.2.0
-    # via ipython
 black==23.10.1
     # via -r requirements-dev.in
 cfgv==3.4.0
@@ -44,7 +42,7 @@ identify==2.5.27
     # via pre-commit
 iniconfig==2.0.0
     # via pytest
-ipython==8.16.1
+ipython==8.17.2
     # via -r requirements-dev.in
 jedi==0.19.0
     # via ipython
@@ -64,8 +62,6 @@ pathspec==0.11.2
     # via black
 pexpect==4.8.0
     # via ipython
-pickleshare==0.7.5
-    # via ipython
 platformdirs==3.10.0
     # via
     #   black
diff --git a/src/dispatch/static/dispatch/components.d.ts b/src/dispatch/static/dispatch/components.d.ts
index 938f0eda76fa..20b7ce8714af 100644
--- a/src/dispatch/static/dispatch/components.d.ts
+++ b/src/dispatch/static/dispatch/components.d.ts
@@ -25,6 +25,7 @@ declare module '@vue/runtime-core' {
     MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
     NotificationSnackbarsWrapper: typeof import('./src/components/NotificationSnackbarsWrapper.vue')['default']
     PageHeader: typeof import('./src/components/PageHeader.vue')['default']
+    ParticipantSelect: typeof import('./src/components/ParticipantSelect.vue')['default']
     Refresh: typeof import('./src/components/Refresh.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
diff --git a/src/dispatch/static/dispatch/src/case/DetailsTab.vue b/src/dispatch/static/dispatch/src/case/DetailsTab.vue
index 99786988c9d8..0dd2acc99f0d 100644
--- a/src/dispatch/static/dispatch/src/case/DetailsTab.vue
+++ b/src/dispatch/static/dispatch/src/case/DetailsTab.vue
@@ -131,7 +131,7 @@ import CaseSeveritySelect from "@/case/severity/CaseSeveritySelect.vue"
 import CaseTypeSelect from "@/case/type/CaseTypeSelect.vue"
 import DateTimePickerMenu from "@/components/DateTimePickerMenu.vue"
 import IncidentFilterCombobox from "@/incident/IncidentFilterCombobox.vue"
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 import ProjectSelect from "@/project/ProjectSelect.vue"
 import TagFilterAutoComplete from "@/tag/TagFilterAutoComplete.vue"
 
diff --git a/src/dispatch/static/dispatch/src/case/HandoffDialog.vue b/src/dispatch/static/dispatch/src/case/HandoffDialog.vue
index 05e65c5801ba..b786ff8b54a8 100644
--- a/src/dispatch/static/dispatch/src/case/HandoffDialog.vue
+++ b/src/dispatch/static/dispatch/src/case/HandoffDialog.vue
@@ -31,7 +31,7 @@
 import { mapFields } from "vuex-map-fields"
 import { mapActions } from "vuex"
 
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 
 export default {
   name: "CaseHandoffDialog",
diff --git a/src/dispatch/static/dispatch/src/case/ParticipantSelect.vue b/src/dispatch/static/dispatch/src/case/ParticipantSelect.vue
deleted file mode 100644
index 0e8fbc57e67a..000000000000
--- a/src/dispatch/static/dispatch/src/case/ParticipantSelect.vue
+++ /dev/null
@@ -1,127 +0,0 @@
-<template>
-  <v-combobox
-    :items="items"
-    :label="label"
-    :loading="loading"
-    v-model:search="search"
-    @update:search="getFilteredData()"
-    chips
-    clearable
-    closable-chips
-    hide-selected
-    item-title="individual.name"
-    :item-props="(item) => ({ subtitle: item.individual.email })"
-    no-filter
-    return-object
-    v-model="participant"
-  >
-    <template #no-data>
-      <v-list-item>
-        <v-list-item-title>
-          No individuals matching "
-          <strong>{{ search }}</strong
-          >".
-        </v-list-item-title>
-      </v-list-item>
-    </template>
-    <template #append-item>
-      <v-list-item v-if="more" @click="loadMore()">
-        <v-list-item-subtitle> Load More </v-list-item-subtitle>
-      </v-list-item>
-    </template>
-  </v-combobox>
-</template>
-
-<script>
-import { cloneDeep, debounce } from "lodash"
-
-import SearchUtils from "@/search/utils"
-import IndividualApi from "@/individual/api"
-
-export default {
-  name: "ParticipantSelect",
-  props: {
-    modelValue: {
-      type: Object,
-      default: function () {
-        return null
-      },
-    },
-    label: {
-      type: String,
-      default: function () {
-        return "Participant"
-      },
-    },
-  },
-
-  data() {
-    return {
-      loading: false,
-      items: [],
-      more: false,
-      numItems: 5,
-      search: null,
-    }
-  },
-
-  computed: {
-    participant: {
-      get() {
-        return cloneDeep(this.modelValue)
-      },
-      set(value) {
-        this.$emit("update:modelValue", value)
-      },
-    },
-  },
-
-  created() {
-    this.fetchData()
-  },
-
-  methods: {
-    loadMore() {
-      this.numItems = this.numItems + 5
-      this.fetchData()
-    },
-    fetchData() {
-      this.loading = "error"
-      let filterOptions = {
-        q: this.search,
-        sortBy: ["name"],
-        descending: [false],
-        itemsPerPage: this.numItems,
-      }
-
-      if (this.project) {
-        filterOptions = {
-          ...filterOptions,
-          filters: {
-            project: [this.project],
-          },
-        }
-        filterOptions = SearchUtils.createParametersFromTableOptions({ ...filterOptions })
-      }
-
-      IndividualApi.getAll(filterOptions).then((response) => {
-        this.items = response.data.items.map(function (x) {
-          return { individual: x }
-        })
-        this.total = response.data.total
-
-        if (this.items.length < this.total) {
-          this.more = true
-        } else {
-          this.more = false
-        }
-
-        this.loading = false
-      })
-    },
-    getFilteredData: debounce(function () {
-      this.fetchData()
-    }, 500),
-  },
-}
-</script>
diff --git a/src/dispatch/static/dispatch/src/components/ParticipantSelect.vue b/src/dispatch/static/dispatch/src/components/ParticipantSelect.vue
new file mode 100644
index 000000000000..f7fc562aae18
--- /dev/null
+++ b/src/dispatch/static/dispatch/src/components/ParticipantSelect.vue
@@ -0,0 +1,139 @@
+<template>
+  <v-autocomplete
+    :items="items"
+    :label="labelProp"
+    :loading="loading"
+    v-model:search="search"
+    clearable
+    hide-selected
+    item-title="individual.name"
+    item-value="individual.id"
+    return-object
+    chips
+    :hide-no-data="false"
+    v-model="participant"
+    @update:modelValue="handleClear"
+  >
+    <template #no-data>
+      <v-list-item v-if="!loading">
+        <v-list-item-title>
+          No individuals matching <strong>"{{ search }}".</strong>
+        </v-list-item-title>
+      </v-list-item>
+    </template>
+    <template #item="{ props, item }">
+      <v-list-item v-bind="props" :subtitle="item.raw.individual.email" />
+    </template>
+    <template #append-item v-if="items.length < total.value">
+      <v-list-item @click="loadMore()">
+        <v-list-item-subtitle> Load More </v-list-item-subtitle>
+      </v-list-item>
+    </template>
+    <template #chip="data">
+      <v-chip v-bind="data.props" pill>
+        <template #prepend>
+          <v-avatar color="teal" start> {{ initials(data.item.title) }} </v-avatar>
+        </template>
+        {{ data.item.title }}
+      </v-chip>
+    </template>
+  </v-autocomplete>
+</template>
+
+<script>
+import { ref, watch, toRefs, onMounted } from "vue"
+import { initials } from "@/filters"
+import { debounce } from "lodash"
+
+import IndividualApi from "@/individual/api"
+
+export default {
+  name: "ParticipantSelect",
+  props: {
+    labelProp: {
+      // Define the labelProp
+      type: String,
+      default: "Participant",
+    },
+    initialValue: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  setup(props) {
+    const { labelProp } = toRefs(props) // toRefs make props reactive
+
+    let loading = ref(false)
+    let items = ref([])
+    console.log(items)
+    let numItems = ref(10)
+    let participant = ref({ ...props.initialValue })
+    let currentPage = ref(1)
+    let total = ref(0)
+    const search = ref(props.initialValue.name)
+
+    let debouncedGetIndividualData = null
+
+    const getIndividualData = async (searchVal, page = currentPage.value) => {
+      loading.value = true
+      let filterOptions = {
+        q: searchVal,
+        sortBy: ["name"],
+        descending: [false],
+        itemsPerPage: numItems.value * page,
+      }
+
+      await IndividualApi.getAll(filterOptions).then((response) => {
+        console.log(response.data.items)
+        items.value = response.data.items.map(function (x) {
+          return { individual: x }
+        })
+        total.value = response.data.total
+      })
+
+      loading.value = false
+    }
+
+    onMounted(() => {
+      debouncedGetIndividualData = debounce(getIndividualData, 300)
+      debouncedGetIndividualData(search.value)
+    })
+
+    const loadMore = async () => {
+      currentPage.value++
+      numItems.value += 10
+      await debouncedGetIndividualData(search.value)
+    }
+
+    const handleClear = (newValue) => {
+      if (!newValue) {
+        items.value = []
+        search.value = null
+        participant.value = null
+        numItems.value = 10
+        currentPage.value = 1
+      }
+    }
+
+    watch(search, async (newVal, oldVal) => {
+      if (oldVal !== newVal) {
+        numItems.value = 10
+        await debouncedGetIndividualData(newVal)
+      }
+    })
+
+    return {
+      getIndividualData,
+      handleClear,
+      initials,
+      items,
+      labelProp,
+      loading,
+      loadMore,
+      participant,
+      search,
+      total,
+    }
+  },
+}
+</script>
diff --git a/src/dispatch/static/dispatch/src/dashboard/incident/IncidentDialogFilter.vue b/src/dispatch/static/dispatch/src/dashboard/incident/IncidentDialogFilter.vue
index 7dac62efee00..97875ed28629 100644
--- a/src/dispatch/static/dispatch/src/dashboard/incident/IncidentDialogFilter.vue
+++ b/src/dispatch/static/dispatch/src/dashboard/incident/IncidentDialogFilter.vue
@@ -75,7 +75,7 @@ import ProjectCombobox from "@/project/ProjectCombobox.vue"
 import RouterUtils from "@/router/utils"
 import SearchUtils from "@/search/utils"
 import TagFilterAutoComplete from "@/tag/TagFilterAutoComplete.vue"
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 
 let today = function () {
   let now = new Date()
diff --git a/src/dispatch/static/dispatch/src/incident/DetailsTab.vue b/src/dispatch/static/dispatch/src/incident/DetailsTab.vue
index cb646216cc9e..4a8914fc5561 100644
--- a/src/dispatch/static/dispatch/src/incident/DetailsTab.vue
+++ b/src/dispatch/static/dispatch/src/incident/DetailsTab.vue
@@ -125,7 +125,7 @@ import IncidentFilterCombobox from "@/incident/IncidentFilterCombobox.vue"
 import IncidentPrioritySelect from "@/incident/priority/IncidentPrioritySelect.vue"
 import IncidentSeveritySelect from "@/incident/severity/IncidentSeveritySelect.vue"
 import IncidentTypeSelect from "@/incident/type/IncidentTypeSelect.vue"
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 import ProjectSelect from "@/project/ProjectSelect.vue"
 import TagFilterAutoComplete from "@/tag/TagFilterAutoComplete.vue"
 
diff --git a/src/dispatch/static/dispatch/src/incident/HandoffDialog.vue b/src/dispatch/static/dispatch/src/incident/HandoffDialog.vue
index 2cc96a6cc948..b7756a990b0d 100644
--- a/src/dispatch/static/dispatch/src/incident/HandoffDialog.vue
+++ b/src/dispatch/static/dispatch/src/incident/HandoffDialog.vue
@@ -38,7 +38,7 @@
 import { mapFields } from "vuex-map-fields"
 import { mapActions } from "vuex"
 
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 
 export default {
   name: "IncidentHandoffDialog",
diff --git a/src/dispatch/static/dispatch/src/incident/ParticipantSelect.vue b/src/dispatch/static/dispatch/src/incident/ParticipantSelect.vue
deleted file mode 100644
index ea5dbeaa1393..000000000000
--- a/src/dispatch/static/dispatch/src/incident/ParticipantSelect.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-<template>
-  <v-combobox
-    :items="items"
-    :label="label"
-    :loading="loading"
-    v-model:search="search"
-    @update:search="getFilteredData()"
-    chips
-    clearable
-    hide-selected
-    item-title="individual.name"
-    no-filter
-    return-object
-    v-model="participant"
-  >
-    <template #no-data>
-      <v-list-item>
-        <v-list-item-title>
-          No individuals matching "
-          <strong>{{ search }}</strong
-          >".
-        </v-list-item-title>
-      </v-list-item>
-    </template>
-    <template #item="{ props, item }">
-      <v-list-item v-bind="props" :subtitle="item.raw.individual.email" />
-    </template>
-    <template #append-item>
-      <v-list-item v-if="more" @click="loadMore()">
-        <v-list-item-subtitle> Load More </v-list-item-subtitle>
-      </v-list-item>
-    </template>
-  </v-combobox>
-</template>
-
-<script>
-import { cloneDeep, debounce } from "lodash"
-
-import SearchUtils from "@/search/utils"
-import IndividualApi from "@/individual/api"
-
-export default {
-  name: "ParticipantSelect",
-  props: {
-    modelValue: {
-      type: Object,
-      default: function () {
-        return null
-      },
-    },
-    label: {
-      type: String,
-      default: function () {
-        return "Participant"
-      },
-    },
-  },
-
-  data() {
-    return {
-      loading: false,
-      items: [],
-      more: false,
-      numItems: 5,
-      search: null,
-    }
-  },
-
-  computed: {
-    participant: {
-      get() {
-        return cloneDeep(this.modelValue)
-      },
-      set(value) {
-        this.$emit("update:modelValue", value)
-      },
-    },
-  },
-
-  created() {
-    this.fetchData()
-  },
-
-  methods: {
-    loadMore() {
-      this.numItems = this.numItems + 5
-      this.fetchData()
-    },
-    fetchData() {
-      this.loading = "error"
-      let filterOptions = {
-        q: this.search,
-        sortBy: ["name"],
-        descending: [false],
-        itemsPerPage: this.numItems,
-      }
-
-      if (this.project) {
-        filterOptions = {
-          ...filterOptions,
-          filters: {
-            project: [this.project],
-          },
-        }
-        filterOptions = SearchUtils.createParametersFromTableOptions({ ...filterOptions })
-      }
-
-      IndividualApi.getAll(filterOptions).then((response) => {
-        this.items = response.data.items.map(function (x) {
-          return { individual: x }
-        })
-        this.total = response.data.total
-
-        if (this.items.length < this.total) {
-          this.more = true
-        } else {
-          this.more = false
-        }
-
-        this.loading = false
-      })
-    },
-    getFilteredData: debounce(function () {
-      this.fetchData()
-    }, 500),
-  },
-}
-</script>
diff --git a/src/dispatch/static/dispatch/src/incident/TableFilterDialog.vue b/src/dispatch/static/dispatch/src/incident/TableFilterDialog.vue
index 2357d35f3719..6d03d3b392cb 100644
--- a/src/dispatch/static/dispatch/src/incident/TableFilterDialog.vue
+++ b/src/dispatch/static/dispatch/src/incident/TableFilterDialog.vue
@@ -78,7 +78,7 @@ import IncidentTypeCombobox from "@/incident/type/IncidentTypeCombobox.vue"
 import ProjectCombobox from "@/project/ProjectCombobox.vue"
 import TagFilterAutoComplete from "@/tag/TagFilterAutoComplete.vue"
 import TagTypeFilterCombobox from "@/tag_type/TagTypeFilterCombobox.vue"
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 
 export default {
   name: "IncidentTableFilterDialog",
diff --git a/src/dispatch/static/dispatch/src/task/NewEditSheet.vue b/src/dispatch/static/dispatch/src/task/NewEditSheet.vue
index b67cbb63c5c9..6e39e33928e9 100644
--- a/src/dispatch/static/dispatch/src/task/NewEditSheet.vue
+++ b/src/dispatch/static/dispatch/src/task/NewEditSheet.vue
@@ -107,7 +107,7 @@ import { mapActions } from "vuex"
 
 import ProjectSelect from "@/project/ProjectSelect.vue"
 import IncidentSelect from "@/incident/IncidentSelect.vue"
-import ParticipantSelect from "@/incident/ParticipantSelect.vue"
+import ParticipantSelect from "@/components/ParticipantSelect.vue"
 import AssigneeCombobox from "@/task/AssigneeCombobox.vue"
 import DateTimePickerMenu from "@/components/DateTimePickerMenu.vue"