File size: 6,578 Bytes
d741351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import gradio as gr
import plotly.graph_objects as go
import random
import numpy as np
import requests
import os
from huggingface_hub import InferenceClient

HF_TOKEN = os.environ.get("HF_TOKEN")
client = InferenceClient(api_key=HF_TOKEN)

def generate_response(system_prompt, user_prompt):
    temperature, top_p = random.uniform(0.6, 0.75), random.uniform(0.7, 1.0)
    completion = client.chat.completions.create(
        model="mistralai/Mistral-Nemo-Instruct-2407",
        messages=[{
            "role": "system",
            "content": system_prompt
        }, {
            "role": "user",
            "content": user_prompt
        }],
        temperature=temperature,
        max_tokens=1024,
        top_p=top_p,
    )

    return completion.choices[0].message.content

def generate_location_recommendations(user_prompt):

    system_prompt = """
    你是一個專業的地點推薦助手,專門根據使用者的需求提供相關的知名景點或位置。當使用者詢問某個地點、旅遊目的或活動建議時,請根據指令找出適合的多個景點,並**務必滿足使用者的詢問需求**。回應需遵守以下規則:

    1. **以條列式列出景點名稱**,每行一個景點名稱。
    2. **不要提供任何額外描述、編號或其他與地點無關的文字**,僅列出名稱即可。
    3. 提供的景點必須是**真實存在**的,而非虛構或杜撰的。
    3. 所有回應須使用**繁體中文**。
    4. **完全滿足**使用者的發問需求。

    ### 回應格式範例:

    ```
    景點 1
    景點 2
    景點 3
    ```

    請確保提供的景點與使用者查詢密切相關,並確認所有景點真實存在,且涵蓋足夠的選項以滿足使用者的需求。"""

    completion = generate_response(system_prompt, user_prompt)

    locations = []
    for loc in completion.split('\n'):
        lat_lon = fetch_location_coordinates(loc)
        locations.append(lat_lon)

    # 刪除找不到的地點
    locations = np.array([loc for loc in locations if loc[1] is not None])

    return user_prompt, locations

def generate_travel_summary(user_prompt, locations):

    system_prompt = """
    你是一個專業且富有魅力的旅遊與地點推薦助手。你的任務是根據**使用者的原始提問**和提供的**景點或地點清單**,生成一段專業、豐富且引人入勝的結論。這段結論應讓使用者感到興奮、滿足或有所啟發,並進一步激發他們的興趣。

    請遵循以下指引:

    1. **根據使用者的需求和目的**,適當總結景點或地點清單,並提供有趣的背景或亮點描述。
    2. **保持語氣專業且富有吸引力**,可以適度加入生動的形容詞,讓建議更具畫面感和感染力。
    3. **結尾可以提出鼓勵性或引導性的語句**,促使用者更期待他們的旅程或任務。
    4. **回應須使用繁體中文**,並保持流暢易讀。

    輸入格式:

    使用者提問:「問題內容」
    景點清單:
    ```
    地點 1
    地點 2
    地點 3
    ```

    輸出格式 (輸出內容**僅包含結論**,**無須重複**提問內容或其他無關的文字):
    你的結論
    """

    user_prompt = f"""使用者提問:「{user_prompt}
    景點清單:
    ```
    {locations}
    ```\n\n"""

    completion = generate_response(system_prompt, user_prompt)

    return completion

def fetch_location_coordinates(location):
    location_response = requests.get(
        f"https://nominatim.openstreetmap.org/search?q={location}&format=json&limit=1",
        headers={
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
            "Accept-Language": "zh-TW,zh;q=0.7",
            "Cache-Control": "max-age=0",
            "Priority": "u=0, i",
            "Sec-Ch-Ua-Mobile": "?1",
            "Sec-Ch-Ua-Platform": "\"Android\"",
            "Sec-Fetch-Dest": "document",
            "Sec-Fetch-Mode": "navigate",
            "Sec-Fetch-Site": "none",
            "Sec-Fetch-User": "?1",
            "Sec-Gpc": "1",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36^"
        }
    )
    if location_response.status_code == 200 and len(location_response.json()) > 0:
        loc = location_response.json()[0]
        loc = [location, float(loc['lat']), float(loc['lon'])]
        return loc
    else:
        return [location, None, None]

def plot_locations(locations):
    if len(locations) == 0:
        return "No locations found. Please try another query."

    names = locations[:, 0]
    lats = locations[:, 1].astype(float)
    lons = locations[:, 2].astype(float)

    fig = go.Figure(go.Scattermapbox(
        lat=lats,
        lon=lons,
        mode='markers',
        marker=go.scattermapbox.Marker(size=14, color='red'),
        text=names,
        hoverinfo="text"
    ))

    fig.update_layout(
        mapbox_style="open-street-map",
        mapbox=dict(
            center=go.layout.mapbox.Center(
                # 以台灣中心點為地圖中心
                lat=23.905987823498418,
                lon=121.08291334460809
            ),
            zoom=6
        ),
        margin={"r": 0, "t": 0, "l": 0, "b": 0}
    )

    return fig

def handle_user_request(text):
    # 生成地點建議
    suggestions = generate_location_recommendations(text)
    if len(suggestions[1]) > 0:
        # 若有找到地點,則使用這份清單,生成一段行程指引
        output = generate_travel_summary(suggestions[0], "\n".join(suggestions[1][:, 0]))
        map_figure = plot_locations(suggestions[1])
        return output, map_figure
    else:
        return "No locations found for the given input. Please try another query.", "No locations found."

# Gradio 介面
with gr.Blocks() as app:
    gr.Markdown("# 🌏 ChatMap: Your AI-Powered Map Companion 🌏")

    with gr.Row():
        text_input = gr.Textbox(label="Tell me your command", value="我要去宜蘭玩,請推薦我至少5個熱門景點")

    submit_btn = gr.Button("Generate")

    output_summary = gr.Textbox(label="Here’s what I’ve mapped out for you", interactive=False)
    map_display = gr.Plot(label="Visualizing your journey on the map")

    submit_btn.click(handle_user_request, inputs=text_input, outputs=[output_summary, map_display])

# 啟動應用
app.launch()